package top.suven.http.oauth.contorller;


import com.alibaba.fastjson.JSONObject;
import org.apache.oltu.oauth2.as.request.OAuthUnauthenticatedTokenRequest;
import org.apache.oltu.oauth2.as.response.OAuthASResponse;
import org.apache.oltu.oauth2.client.OAuthClient;
import org.apache.oltu.oauth2.client.URLConnectionClient;
import org.apache.oltu.oauth2.client.request.OAuthBearerClientRequest;
import org.apache.oltu.oauth2.client.request.OAuthClientRequest;
import org.apache.oltu.oauth2.client.response.OAuthAuthzResponse;
import org.apache.oltu.oauth2.client.response.OAuthJSONAccessTokenResponse;
import org.apache.oltu.oauth2.client.response.OAuthResourceResponse;
import org.apache.oltu.oauth2.common.OAuth;
import org.apache.oltu.oauth2.common.exception.OAuthProblemException;
import org.apache.oltu.oauth2.common.exception.OAuthSystemException;
import org.apache.oltu.oauth2.common.message.OAuthResponse;
import org.apache.oltu.oauth2.common.message.types.GrantType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import top.suven.base.http.enums.SysMsgEnumType;
import top.suven.base.http.handler.OutputMessage;
import top.suven.base.http.processor.url.URLCommand;
import top.suven.base.util.utilize.JsonUtils;
import top.suven.http.oauth.authorize.OAuthAuthorizeCodeHandler;
import top.suven.http.oauth.facade.OauthServiceFacade;
import top.suven.http.oauth.token.OAuthAuthxRequest;
import top.suven.http.oauth.token.OAuthTokenHandleDispatcher;
import top.suven.http.oauth.vo.ConstantKey;
import top.suven.http.oauth.vo.OAuthTokenBuilder;
import top.suven.http.oauth.vo.request.OAuthCheckTokenRequestVo;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * 2018-08-01
 * <p/>
 * 对各类 OauthAuthorizeController 的请求进行验证的公共类
 * 将通用的行为(方法) 位于此
 * 所有支持的的授权类型(grand_type):
 *    AUTHORIZATION_CODE("authorization_code"),
 *     IMPLICIT("implicit"),
 *     PASSWORD("password"),
 *     REFRESH_TOKEN("refresh_token"),
 *     CLIENT_CREDENTIALS("client_credentials"),
 *     JWT_BEARER("urn:ietf:params:oauth:grant-type:jwt-bearer");
 *
 *     authorization_code — 授权码模式(即先登录获取code,再获取token)
 *     password — 密码模式(将用户名,密码传过去,直接获取token)
 *     client_credentials — 客户端模式(无用户,用户向客户端注册,然后客户端以自己的名义向’服务端’获取资源)
 *     implicit — 简化模式(在redirect_uri 的Hash传递token; Auth客户端运行在浏览器中,如JS,Flash)
 *     refresh_token — 刷新access_token
 *
 *     OAuthTokenRequest 支持的授权类型有:
 *     PASSWORD("password"), 对用户名密码进行校验
 *     REFRESH_TOKEN("refresh_token"), 刷新token 校验
 *     CLIENT_CREDENTIALS("client_credentials"), 客户端证书校验
 *     AUTHORIZATION_CODE("authorization_code"), 鉴权code校验
 * @author suven.wang
 */
@Controller
public class OAuthAuthorizeController {

    @Autowired
    private OauthServiceFacade oauthServiceFacade;


    private static final Logger logger = LoggerFactory.getLogger(OAuthAuthorizeController.class);


    /**
     * Authorization Code 授权码模式
     * Must handle the grant_type as follow:
     * <p/>
     * grant_type="authorization_code" -> response_type="code"
     * ?response_type=code&scope=read,write&client_id=[client_id]&redirect_uri=[redirect_uri]&state=[state]
     * <p/>
     *
     * @param request  HttpServletRequest
     */
    @RequestMapping(value = URLCommand.auth_post_authorize,method = RequestMethod.GET)
    public void authorizeByAuthorization_code(OutputMessage out, HttpServletRequest request) throws OAuthSystemException, ServletException, IOException {
        try {

            OAuthAuthxRequest oauthRequest = new OAuthAuthxRequest(request);

            OAuthAuthorizeCodeHandler codeAuthorizeHandler = new OAuthAuthorizeCodeHandler();
                logger.debug("Go to  response_type = 'code' handler: {}", codeAuthorizeHandler);
            boolean ResponseStatus = codeAuthorizeHandler.handle(oauthRequest);
            JSONObject result = codeAuthorizeHandler.getResponse();
            if(ResponseStatus){
                out.write(result);
            }else {
               out.writeAuth(SysMsgEnumType.SYS_UNKOWNN_FAIL,result) ;
            }
        } catch (OAuthProblemException e) {
            OAuthResponse oAuthResponse = OAuthASResponse
                    .errorResponse(HttpServletResponse.SC_FOUND)
                    .location(e.getRedirectUri())
                    .error(e)
                    .buildJSONMessage();
            out.writeAuth(SysMsgEnumType.AUTH_PROBLEM_EXCEPTION,writeResponse(oAuthResponse));
        }

    }

    /**
     * Handle grant_types as follows:
     * <p/>
     * grant_type=authorization_code
     * grant_type=password
     * grant_type=refresh_token
     * grant_type=client_credentials
     *  <p/>
     * grant_type="implicit"   -> response_type="token"
     * ?response_type=token&scope=read,write&client_id=[client_id]&client_secret=[client_secret]&redirect_uri=[redirect_uri]
     * <p/>
     * @param request  HttpServletRequest
     * @throws OAuthSystemException
     */
//    @RequestMapping(value = URLCommand.auth_post_token,method = RequestMethod.POST)
    @RequestMapping(value = URLCommand.auth_post_token)
    public void authorize(OutputMessage out,HttpServletRequest request) throws OAuthSystemException {
        try {
            OAuthUnauthenticatedTokenRequest tokenRequest = new OAuthUnauthenticatedTokenRequest(request);

            OAuthTokenHandleDispatcher tokenHandleDispatcher = new OAuthTokenHandleDispatcher(tokenRequest);
            OAuthResponse oAuthResponse = tokenHandleDispatcher.dispatch();
            if(null != oAuthResponse){
                out.write(tokenHandleDispatcher.getResponse(oAuthResponse));
            }else {
                out.write(SysMsgEnumType.SYS_UNKOWNN_FAIL) ;
            }
        } catch (OAuthProblemException e) {
            //exception
            e.printStackTrace();
            OAuthResponse oAuthResponse = OAuthASResponse
                    .errorResponse(HttpServletResponse.SC_FOUND)
                    .location(e.getRedirectUri())
                    .error(e)
                    .buildJSONMessage();
            out.writeAuth(SysMsgEnumType.AUTH_PROBLEM_EXCEPTION,writeResponse(oAuthResponse));
        }

    }
    private JSONObject writeResponse(OAuthResponse response){
        return JsonUtils.readValue(response.getBody(),JSONObject.class);
    }

    @RequestMapping(value = URLCommand.auth_post_check_token,method = RequestMethod.POST)
    public void authorizeByCheck_token(OutputMessage out,OAuthCheckTokenRequestVo checkTokenVo) {
        try {
            OAuthTokenBuilder builder = OAuthTokenBuilder.build()
                    .setUserId(checkTokenVo.getUserId())
                    .setAccessToken(checkTokenVo.getAccessToken());
            boolean result =  oauthServiceFacade.checkUserToken(builder);
            if(result){
                out.write(result);
            }else {
                out.write(SysMsgEnumType.AUTH_ACCESS_TOKEN_EXPIRE);
            }

        } catch (Exception e) {
            e.printStackTrace();
            out.write(SysMsgEnumType.SYS_UNKOWNN_FAIL) ;
        }

    }




    /**
        grant_type：表示使用的授权模式，必选项，此处的值固定为"authorization_code"
        code：表示上一步获得的授权码，必选项。
        redirect_uri：表示重定向URI，必选项，且必须与A步骤中的该参数值保持一致
        client_id：表示客户端ID，必选项
    */
    /**
     * 获得令牌
     * @return oauth_callback?code=1234
     */
//    @RequestMapping(value = "/oauth_callback" ,method = RequestMethod.GET)
    public String getToken(HttpServletRequest request,Model model) throws OAuthProblemException {
        OAuthAuthzResponse oauthAuthzResponse = null;
        try {
            oauthAuthzResponse = OAuthAuthzResponse.oauthCodeAuthzResponse(request);
            String code = oauthAuthzResponse.getCode();
            OAuthClientRequest oauthClientRequest = OAuthClientRequest
                    .tokenLocation(ConstantKey.OAUTH_CLIENT_ACCESS_TOKEN)
                    .setGrantType(GrantType.AUTHORIZATION_CODE)
                    .setClientId(ConstantKey.OAUTH_CLIENT_ID)
                    .setClientSecret(ConstantKey.OAUTH_CLIENT_SECRET)
                    .setRedirectURI(ConstantKey.OAUTH_CLIENT_CALLBACK)
                    .setCode(code)
                    .buildQueryMessage();
            OAuthClient oAuthClient = new OAuthClient(new URLConnectionClient());

            //Facebook is not fully compatible with OAuth 2.0 draft 10, access token response is
            //application/x-www-form-urlencoded, not json encoded so we use dedicated response class for that
            //Custom response classes are an easy way to deal with oauth providers that introduce modifications to
            //OAuth 2.0 specification

            //获取access token
            OAuthJSONAccessTokenResponse oAuthResponse = oAuthClient.accessToken(oauthClientRequest, OAuth.HttpMethod.POST);
            String accessToken = oAuthResponse.getAccessToken();
            String refreshToken= oAuthResponse.getRefreshToken();
            Long expiresIn = oAuthResponse.getExpiresIn();
            //获得资源服务
            OAuthClientRequest bearerClientRequest = new OAuthBearerClientRequest(ConstantKey.OAUTH_CLIENT_GET_RESOURCE)
                    .setAccessToken(accessToken).buildQueryMessage();
            OAuthResourceResponse resourceResponse = oAuthClient.resource(bearerClientRequest, OAuth.HttpMethod.GET, OAuthResourceResponse.class);
            String resBody = resourceResponse.getBody();
            logger.info("accessToken: "+accessToken +" refreshToken: "+refreshToken +" expiresIn: "+expiresIn +" resBody: "+resBody);
            model.addAttribute("accessToken",  "accessToken: "+accessToken + " resBody: "+resBody);
            return "oauth2/token";
        } catch (OAuthSystemException ex) {
            logger.error("getToken OAuthSystemException : " + ex.getMessage());
            model.addAttribute("errorMsg",  ex.getMessage());
            return  "/oauth2/error";
        }
    }





}